package com.onpositive.knowledge.model.value.audits;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import com.onpositive.data.IPropertyConstants;
import com.onpositive.data.units.DateWithAccuracy;
import com.onpositive.knowledge.model.IUniverse;
import com.onpositive.knowledge.model.values.DescribedValue;
import com.onpositive.knowledge.model.values.IHasAuditResult;
import com.onpositive.knowledge.model.values.IHasValue;
import com.onpositive.knowledge.model.values.ValueTraverser;
import com.onpositive.semantic.model.api.property.IProperty;
import com.onpositive.units.ParsedDate;
import com.onpositive.units.ParsedDimension;
import com.onpositive.units.ParsedScalar;
import com.onpositive.units.ParsedTerm;
import com.onpositive.units.RangeValue;
import com.onpositive.units.ValueCluster;
import com.onpositive.utils.NumericValueValidityProvider;

public class StatisticValueAudit implements IValueAudit {

	/**
	 * 
	 */
	
	public static final String ID = "statistic-audit";
	private static final long serialVersionUID = 1L;
	private final static HashSet<String> acceptedValueTypes = new HashSet<String>( 
			Arrays.asList(new String[]{ ParsedScalar.ID, ParsedDimension.ID, ParsedDate.ID }) ) ;
	
	@Override
	public void perform( IProperty prop, Iterable<Object> objects, AbstractAuditResult auditResult ) {
		
		if( auditResult == null || !(auditResult instanceof RangeAuditResult) )
			return ;
		
		String valueType = prop.getMeta().getSingleValue( IPropertyConstants.CHAR_PARSER_ID_LABEL, String.class, null ) ;
		OwnTraverser<?> tr = traverserMap.get( valueType ) ;
		if( tr == null )
			return ;
		
		HashMap<String,Object> metaInfo = new HashMap<String, Object>() ;
		metaInfo.put("audit-result", auditResult ) ;
		ArrayList<Double> valuesArray = new ArrayList<Double>() ;
		metaInfo.put("values-array", valuesArray ) ;
		ArrayList<Double> individualValueArray = new ArrayList<Double>() ;
		metaInfo.put("individual-value-array", individualValueArray ) ;
		tr.iterateObjects(prop, objects, metaInfo, 0) ;
				
		processData( prop, objects, metaInfo ) ;
	}
	
	private HashMap<String,OwnTraverser<?>> traverserMap = new HashMap<String, OwnTraverser<?>>() ;
	{
		traverserMap.put( ParsedDimension.ID, new OwnTraverser<ParsedDimension>( ParsedDimension.class ) ) ;
		traverserMap.put( ParsedScalar.ID, new OwnTraverser<ParsedScalar>( ParsedScalar.class ) ) ;
		traverserMap.put( ParsedDate.ID, new OwnTraverser<ParsedDate>( ParsedDate.class ) ) ;
	}
	private static class OwnTraverser<T extends RangeValue<?>> extends ValueTraverser{
		
		private final Class<T> targetClass ;
		
		private OwnTraverser( Class<T> targetClass ){
			super( new Class<?>[]{ targetClass, ValueCluster.class }, new Class<?>[]{ targetClass, ValueCluster.class } ) ;
			this.targetClass = targetClass ;
		}

		@Override
		protected void postIterateValue(IProperty prop, Object val, Map<String, Object> metaInfo, int index)
		{
			ArrayList<Double> individualValueArray = (ArrayList<Double>) metaInfo.get("individual-value-array") ;			
			if( individualValueArray.isEmpty() )
				return;
			
			double maxVal = Collections.max( individualValueArray ) ;
			double minVal = Collections.min( individualValueArray ) ;
			maxVal = Math.abs(maxVal) ;
			minVal = Math.abs(minVal) ;
					
			double ratio = maxVal/minVal ;
			if( !( Double.isNaN( ratio ) || ratio > 9 ) ){				
			
				ArrayList<Double> valuesArray = (ArrayList<Double>) metaInfo.get("values-array") ;				
				
				for( Double dVal : individualValueArray ){
//					if( prop.getId().equals("power mass") && Math.abs( dVal-960 ) < 0.01 )
//						System.out.println() ;
					
					valuesArray.add( dVal ) ;
				}

			}
			individualValueArray.clear() ;
		}
		
		@Override
		protected void process(IProperty prop, Object val, Map<String, Object> metaInfo, int index)
		{			
			T pVal ;
			if( targetClass.isInstance(val) ){
				pVal = targetClass.cast( val ) ;
			}
			else if( val instanceof ValueCluster ){
				ValueCluster<?> vc = (ValueCluster<?>) val ;
				Class<?> clusterClass = vc.getTargetClass() ;
				if( clusterClass != targetClass )
					return ;
				
				pVal = targetClass.cast( vc.getTargetValue() ) ;
			}
			else
				return ;
			
			Double[] values = pVal.getConvertedValue( Double.class ) ;
						
			Boolean  inspectionMode = (Boolean)  metaInfo.get("inspection-mode") ;
			if( inspectionMode == null || !inspectionMode )
			{			
				ArrayList<Double> individualValuesArray = (ArrayList<Double>) metaInfo.get("individual-value-array") ;							
				for( Double dVal : values ){
					if( dVal == null || Double.isNaN( dVal ) )
						continue ;
				
					individualValuesArray.add( dVal ) ;
				}
				
			}
			else{			
				RangeAuditResult  auditResult = (RangeAuditResult)  metaInfo.get("audit-result") ;
				double lb = auditResult.getLowerBound() ;
				double ub = auditResult.getUpperBound() ;
				
				for( Double dVal : values )
					if( dVal < lb || dVal > ub ){						
						metaInfo.put("is-suspicious", true ) ;
						Object currentObject = metaInfo.get(META_KEY_CURRENT_OBJECT) ;
						auditResult.addAsSuspicious( currentObject ) ;
						break ;
					}
			}
		}
		

		@Override
		protected Class<?>[] createNecessaryClasses() {
			return null;
		}

		@Override
		protected Class<?>[] createTargetClasses() {
			return null;
		}
		
	}

	@Override
	public AbstractAuditResult createAuditResult(IProperty prop)
	{
		Boolean isService = prop.getMeta().getSingleValue( IPropertyConstants.CHAR_IS_SERVICE_PROPERTY, Boolean.class, null ) ;
		if( isService != null && isService )
			return null;
		
		Boolean isDepricated = prop.getMeta().getSingleValue( IPropertyConstants.CHAR_DEPRICATED_LABEL, Boolean.class, null ) ;
		if( isDepricated != null && isDepricated )
			return null;
		
		String valueType = prop.getMeta().getSingleValue( IPropertyConstants.CHAR_PARSER_ID_LABEL, String.class, null ) ;
		if( !acceptedValueTypes.contains( valueType ) )
			return null ;
		
		if( valueType.equals( ParsedDate.ID) ){
			
			return null ;
			
//			RangeAuditResult result = new RangeAuditResult( prop, DateWithAccuracy.class );
//			result.setAuditId(ID) ;
//			return result ;
		}
		
		RangeAuditResult result = new RangeAuditResult( prop, double.class );
		result.setAuditId(ID) ;		
		return result ;	
	}
	
	//@Override
	protected boolean isSuspicious(Object o, DescribedValue value, 	IProperty prop, AbstractAuditResult auditResult )
	{
		if( auditResult == null || !(auditResult instanceof RangeAuditResult) )
			return false;
		
		String valueType = prop.getMeta().getSingleValue( IPropertyConstants.CHAR_PARSER_ID_LABEL, String.class, null ) ;
		OwnTraverser<?> tr = traverserMap.get( valueType ) ;
		if( tr == null )
			return false;
		
		HashMap<String,Object> metaInfo = new HashMap<String, Object>() ;
		metaInfo.put("audit-result", auditResult ) ;
		metaInfo.put("inspection-mode", true ) ;
		tr.iterateObjects(prop, new ArrayList<Object>( Arrays.asList( new Object[]{ value } ) ), metaInfo, 0) ;
		
		Boolean isSuspicious = (Boolean) metaInfo.get("is-suspicious") ;
		if( isSuspicious != null || isSuspicious )
			return true ;
		
		return false ;
	}
	
	@SuppressWarnings("unchecked")
	protected boolean processData( IProperty prop , Iterable<Object> objects, HashMap<String,Object> metaInfo )
	{		
		ArrayList<Double> valuesArray = (ArrayList<Double>) metaInfo.get("values-array") ;
		
		Collections.sort(valuesArray) ;
		
		ArrayList<Double> logValuesArray = new ArrayList<Double>( valuesArray.size() ) ;
		for( Double d : valuesArray )
			logValuesArray.add( Math.log10(d) ) ;
			
		RangeAuditResult  auditResult = (RangeAuditResult)  metaInfo.get("audit-result") ;
		if( valuesArray.size() < 5 )
		{
			auditResult.setLooksApplyable(false) ;
			return false ;
		}
		
		auditResult.setLooksApplyable( true ) ;
		
		System.out.println( auditResult.getPropertyId() + " count = " + valuesArray.size() ) ;
		NumericValueValidityProvider nvvp = new NumericValueValidityProvider( valuesArray, prop.getId() ) ;
		NumericValueValidityProvider nvvp2= new NumericValueValidityProvider( logValuesArray, prop.getId()+ " log" ) ;
		if( !nvvp.isCorrect() )
		{
			auditResult.setLooksApplyable(false) ;
			return false ;
		}
		
		int lowerValidIndex = Math.max( nvvp.getLowerValidIndex(), nvvp2.getLowerValidIndex() ) ;
		int upperValidIndex = Math.min( nvvp.getUpperValidIndex(), nvvp2.getUpperValidIndex() ) ;
		
		double lowerValidBound =  valuesArray.get(lowerValidIndex) - NumericValueValidityProvider.getEpsilon() ;
		double upperValidBound =  valuesArray.get(upperValidIndex) + NumericValueValidityProvider.getEpsilon() ;	
		
		double[] epsilonArray  = nvvp.getEpsilonArray() ;
		auditResult.setEpsilonArray(epsilonArray) ;
		
		auditResult.setLooksApplyable( true ) ;
		
		auditResult.setComment(ID + ", lower bound = " + lowerValidBound + ", upper bound = " + upperValidBound ) ;		
		
		auditResult.setLowerBound( lowerValidBound ) ;		
		auditResult.setUpperBound( upperValidBound ) ;
		
		metaInfo.put( "inspection-mode", true ) ;		
		String valueType = prop.getMeta().getSingleValue( IPropertyConstants.CHAR_PARSER_ID_LABEL, String.class, null ) ;
		OwnTraverser<?> tr = traverserMap.get( valueType ) ;
		if( tr == null )
			return true;
		
		metaInfo.put("audit-result", auditResult ) ;
		tr.iterateObjects(prop, objects, metaInfo, ValueTraverser.PUT_OBJECT_IN_META ) ;
		
		flushInfo( metaInfo ) ;
		return true;
	}
	
	private void flushInfo( HashMap<String,Object> metaInfo ){
		
		ArrayList<Double> valuesArray = (ArrayList<Double>) metaInfo.get("values-array") ;		
		RangeAuditResult  auditResult = (RangeAuditResult)  metaInfo.get("audit-result") ;
		
		String path = "C:/mediawiki/stats/" ;
		new File( path ).mkdirs() ;
		String fileName = path +auditResult.getPropertyId().replaceAll("/", "_") + ".txt" ;
		File f = new File(fileName) ;
		
		try {
			PrintStream ps = new PrintStream( f ) ;
			//int N = pProvider.N ;

			//for( double d : pProvider.getElements() ){
			Collections.sort( valuesArray ) ;
			if( auditResult.getRangeClass().equals(DateWithAccuracy.class) )
			{
				for( double d : valuesArray )
				{
					ps.print( new DateWithAccuracy( (int)(d+0.5) ) ) ;
					ps.println(" " ) ;
				}
				
				ps.println( "\n" ) ;
				
				ps.println( new DateWithAccuracy( (int)(auditResult.getLowerBound()+0.5)) ) ;
				ps.println( "\n" ) ;
				ps.println( new DateWithAccuracy( (int)(auditResult.getUpperBound()+0.5)) ) ;
				ps.println( "\n" ) ;				
			}
			else{
				for( double d : valuesArray )
				{
					ps.print( d ) ;
					ps.println(" " ) ;
				}
				
				ps.println( "\n" ) ;
				
				ps.println( auditResult.getLowerBound() ) ;
				ps.println( "\n" ) ;
				ps.println( auditResult.getUpperBound() ) ;
				ps.println( "\n" ) ;
			}

			ps.close() ;
				
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
		
	
	@Override
	public Set<String> getRequiredAudits() {
		return null;
	}

	@Override
	public String getId() {
		return ID;
	}



	@Override
	public void setResultSource(IHasAuditResult source) {		
	}



	@Override
	public IHasAuditResult getResultSource() {
		return null;
	}
	
	public IUniverse getUniverse() {
		return null;
	}

	public void setUniverse(IUniverse universe) {}



}
